﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace ImageProcessing
{
	public static class Affine
	{
		public static Bitmap rotateImage(Bitmap bmIn,double angle)
		{
			double theta = Math.PI * angle / 180;
			Bitmap bmOut;

			//find four corners
			CustomMatrix matrixRotation = new CustomMatrix(3, 3, new double[] { Math.Cos(theta), -Math.Sin(theta), 0.0, Math.Sin(theta), Math.Cos(theta), 0.0, 0.0, 0.0, 1.0 });
			double[] adCorners = new double[] { 0.0, (double)bmIn.Width, (double)bmIn.Width, 0.0, 0.0, 0.0, (double)bmIn.Height, (double)bmIn.Height, 1.0, 1.0, 1.0, 1.0 };
			CustomMatrix matrixCorners = new CustomMatrix(3, 4, adCorners);
			matrixCorners = matrixRotation * matrixCorners;

			//find x and y shift value
			double tX = matrixCorners.NegMax(0);
			double tY = matrixCorners.NegMax(1);

			//build GInv
			CustomMatrix matrixGInverse = new CustomMatrix(3, 3, new double[] { Math.Cos(-theta), -Math.Sin(-theta),-tX, Math.Sin(-theta), Math.Cos(-theta), -tY, 0,0,1.0});

			//prepare P and P'
			CustomMatrix matrixPPrime = new CustomMatrix(3, 1);
			CustomMatrix matrixP = new CustomMatrix(3, 1);

			//find size of new image
			double t1 = Math.Cos(theta) * bmIn.Width;
			double t2 = Math.Sin(theta) * bmIn.Height;
			double t3 = Math.Sin(theta) * bmIn.Width;
			double t4 = Math.Cos(theta) * bmIn.Height;
			Size rotatedSize = new Size(((int)(bmIn.Height * Math.Cos(theta) + bmIn.Width * Math.Sin(theta))),
						(int)(bmIn.Height * Math.Sin(theta) + bmIn.Width * Math.Cos(theta)));
			if (rotatedSize.Width == 0)
				rotatedSize.Width = (int)(2 * (Math.Abs(bmIn.Height * Math.Cos(theta))));
			if (rotatedSize.Height == 0)
				rotatedSize.Height = (int)(2 * (Math.Abs(bmIn.Height * Math.Cos(theta))));

			//set up new image
			bmOut = new Bitmap(rotatedSize.Width, rotatedSize.Height, bmIn.PixelFormat);

			//rotate and place
			#region Two image pointer setup
			BitmapData destBitData;
			BitmapData srcBitData;
			Rectangle rectSrc = new Rectangle(0, 0, bmIn.Width, bmIn.Height);
			Rectangle rectDest = new Rectangle(0, 0, bmOut.Width, bmOut.Height);

			srcBitData = bmIn.LockBits(rectSrc, ImageLockMode.ReadOnly, bmIn.PixelFormat);
			destBitData = bmOut.LockBits(rectDest, ImageLockMode.WriteOnly, bmOut.PixelFormat);
			int destBytePP = Image.GetPixelFormatSize(bmOut.PixelFormat) /8;
			int srcBytePP = Image.GetPixelFormatSize(bmIn.PixelFormat) / 8;
			IntPtr destPtr = destBitData.Scan0;
			IntPtr srcPtr = srcBitData.Scan0;
			int srcStride = srcBitData.Stride;
			int destStride = destBitData.Stride;

			matrixP.Matrix[2][0] = 1.0;
			matrixPPrime.Matrix[2][0] = 1.0;
			
			#endregion Image bmOut pointer setup
			int k; //for indexing pointer
			int x;
			int srcx,srcy;
			unsafe
			{
				byte* sptr = (byte*)(void*)srcPtr;
				byte* dptr = (byte*)(void*)destPtr;

				for (int i = 0; i < bmOut.Height; i++)
				{
					//matrixPPrime.SetAtLocation(1, 0, (double)i);
					matrixPPrime.Matrix[1][0] = (double)i;
					k = i * destStride;
					for (int j = 0; j < destStride; j++)
					{
						x = j / srcBytePP; //current 'x' pixel
						matrixPPrime.Matrix[0][0] = (double)x;
						matrixP.Matrix[2][0] = 1.0;
						matrixPPrime.Matrix[2][0] = 1.0;
						matrixP = matrixGInverse * matrixPPrime;
						if (matrixP.Matrix[0][0] <= bmIn.Width && matrixP.Matrix[1][0] <= bmIn.Height && matrixP.Matrix[0][0] >= 0 && matrixP.Matrix[1][0] >= 0)
						{
							srcx = (int)matrixP.Matrix[0][0] * srcBytePP + j % destBytePP;
							srcy = (int)matrixP.Matrix[1][0] * srcStride;
							dptr[k + j] = sptr[srcy + srcx];
						}
						else
						{
							if (destBytePP == 4)
								if (j % 4 != 3)
									dptr[k + j] = 0;
								else
									dptr[k + j] = 255;
							else
								dptr[k + j] = 0;
						}
					}
				}
			}
			bmIn.UnlockBits(srcBitData);
			bmOut.UnlockBits(destBitData);

			return bmOut;
			

		}

		//this will perform a horizontal shear
		public static Bitmap shearImage(Bitmap bmIn, double angle)
		{
			double theta = Math.PI * angle / 180;
			int nNewWidth;

			int[] translationTable = new int[bmIn.Height];
			Bitmap bmOut;

			//create translation table
			double slope = Math.Tan(theta);
			double change = 0.0;
			int nX = 0;

			for (int i = bmIn.Height - 1; i >= 0; i--)
			{
				change += slope;
				if (change > 1)
				{
					nX++;
					change--;
				}
				translationTable[i] = nX;
			}
			//translate image
			nNewWidth = bmIn.Width + translationTable[0];

			//create output bitmap
			bmOut = new Bitmap(nNewWidth, bmIn.Height, bmIn.PixelFormat);
			#region Image pointer setup
			BitmapData srcBitData;
			BitmapData destBitData;
			Rectangle rectSrc = new Rectangle(0, 0, bmIn.Width, bmIn.Height);
			Rectangle rectDest = new Rectangle(0, 0, bmOut.Width, bmOut.Height);
			srcBitData = bmIn.LockBits(rectSrc, ImageLockMode.ReadOnly, bmIn.PixelFormat);
			destBitData = bmOut.LockBits(rectDest, ImageLockMode.WriteOnly, bmOut.PixelFormat);

			int strideSrc = srcBitData.Stride;
			int strideDest = destBitData.Stride;

			IntPtr srcPtr = srcBitData.Scan0;
			IntPtr destPtr = destBitData.Scan0;
			int imageBPP = Image.GetPixelFormatSize(bmIn.PixelFormat);
			Console.WriteLine(imageBPP);
			#endregion Image pointer setup

			int nOffsetSrc;
			int nOffsetDest;
			int nTransOffset;
			unsafe
			{
				byte* readPixels = (byte*)(void*)srcPtr;
				byte* writePixels = (byte*)(void*)destPtr;
				for (int i = 0; i < bmIn.Height; i++)
				{
					nOffsetSrc = i * strideSrc;
					nOffsetDest = i * strideDest;
					nTransOffset = translationTable[i] * (imageBPP / 8);
					for (int j = 0; j < strideSrc; j++)
					{
						if (imageBPP == 32)
						{
							//skip alpha channel if it exists
							if (j % 4 != 3)
							{
								writePixels[j + nOffsetDest + nTransOffset] = readPixels[j + nOffsetSrc];
							}
							else
							{
								writePixels[j + nOffsetDest + nTransOffset] = 255;
							}
						}
						else
						{
							writePixels[j + nOffsetDest + nTransOffset] = readPixels[j + nOffsetSrc];
						}
					} // end for
				} // end for 
				bmIn.UnlockBits(srcBitData);
				bmOut.UnlockBits(destBitData);
			} // end unsafe
			return bmOut;


		}

		public static Bitmap scaleImage(Bitmap bmIn, double scaleWidth, double scaleHeight)
		{
			int nNewWidth;
			int nNewHeight;
			Bitmap bmOut;

			nNewWidth = (int)((double)bmIn.Width * scaleWidth);
			nNewHeight = (int)((double)bmIn.Height * scaleHeight);


			//create output bitmap
			bmOut = new Bitmap(nNewWidth, nNewHeight, bmIn.PixelFormat);
			#region Image pointer setup
			BitmapData srcBitData;
			BitmapData destBitData;
			Rectangle rectSrc = new Rectangle(0, 0, bmIn.Width, bmIn.Height);
			Rectangle rectDest = new Rectangle(0, 0, bmOut.Width, bmOut.Height);
			srcBitData = bmIn.LockBits(rectSrc, ImageLockMode.ReadOnly, bmIn.PixelFormat);
			destBitData = bmOut.LockBits(rectDest, ImageLockMode.WriteOnly, bmOut.PixelFormat);

			int strideSrc = srcBitData.Stride;
			int strideDest = destBitData.Stride;

			IntPtr srcPtr = srcBitData.Scan0;
			IntPtr destPtr = destBitData.Scan0;
			int imageBPP = Image.GetPixelFormatSize(bmIn.PixelFormat);
			int srcBytePP = imageBPP/8;
			Console.WriteLine(imageBPP);
			#endregion Image pointer setup

			int k;
			int srcx, srcy;
			int m;


			unsafe {
				byte* readPixels = (byte*)(void*)srcPtr;
				byte* writePixels = (byte*)(void*)destPtr;

					for (int i = 0; i < bmOut.Height; i++)
					{
						k = i * strideDest;
						srcy = (int)((double)i / scaleHeight)*strideSrc;
						for (int j = 0; j < strideDest; j++)
						{
							//get x pixel
							m = (j / srcBytePP);

							if (srcBytePP == 4)
							{
								if (j % srcBytePP != 3)
								{
									srcx = (int)(Math.Floor((double)m / scaleWidth))*srcBytePP + j % srcBytePP;
									writePixels[j + k] = readPixels[srcx + srcy];
								}//end if
								else
									writePixels[j + k] = 255;
							}//end if
							else
							{
								srcx = (int)(Math.Floor((double)m / scaleWidth))*srcBytePP + j % srcBytePP;
								writePixels[j + k] = readPixels[srcx + srcy];
							}
							
						}//end for
					} //end for

			} //end unsafe
			bmIn.UnlockBits(srcBitData);
			bmOut.UnlockBits(destBitData);
			return bmOut;
			
		}

	}
}
